package com.graduation_design.modules.app.interceptor;

import cn.hutool.core.lang.Assert;
import com.graduation_design.common.lang.Result;
import com.graduation_design.modules.app.annotation.NoRepeatSubmit;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

/**
 * 定义一个切面，拦截该注解的方法
 */
@Aspect
@Slf4j
@Component
public class RepeatSubmitAspect {

    @Resource
    RedissonClient redissonClient;

    @Pointcut("@annotation(noRepeatSubmit)")
    public void pointCut(NoRepeatSubmit noRepeatSubmit) {
    }

    // 切面使用了@Around环绕通知的处理方式，该注解表示在目标方法前后执行操作
    @Around("pointCut(noRepeatSubmit)")
    public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
        int lockSeconds = noRepeatSubmit.lockTime();

        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();

        Assert.notNull(request, "request can not null");

        String token = request.getHeader("token");
        String path = request.getServletPath();
        // key使用token+path，同时指定用户和接口
        String key = token + path;

        RLock lock = redissonClient.getLock(key);

        // 锁会在lockSeconds秒之后自动释放，在期间会获取锁失败
        // 当isSuccess为true的时候，表示可以获得锁，这样才能执行目标方法。
        // 并且通过tryLock方法指定的参数：0表示立即获取锁，lockSeconds表示多少秒之后自动释放锁
        boolean isSuccess = lock.tryLock(0, lockSeconds, TimeUnit.SECONDS);
        if (isSuccess) {

            log.info("tryLock success, key = [{}]", key);
            // 获取锁成功，执行进程
            return pjp.proceed();
        } else {

            // 获取锁失败，认为是重复提交的请求
            log.info("tryLock fail, key = [{}]", key);
            return Result.fail("重复请求，请稍后再试");
        }
    }
}
